﻿using System;
using System.Reflection;
using FakeItEasy;
using NJabber.Client.StanzaFactories;
using NJabber.Stream.Elements;
using NUnit.Framework;

namespace NJabber.Client
{
    [TestFixture]
    public class PresenceStanzaHandlerTest
    {
        private JID fromJid = "from@jid.com";

        private PresenceStanzaHandler handler;
        private PresenceStatusStorage statusStorage;
        private PresenceSubscriptionRegistry subscriptionRegistry;

        [SetUp]
        public void Setup()
        {
            statusStorage = new PresenceStatusStorage();
            subscriptionRegistry = new PresenceSubscriptionRegistry(A.Fake<IPresenceSubscriptionResponder>());
            handler = new PresenceStanzaHandler(statusStorage, subscriptionRegistry);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProcessStanza_StanzaValueIsNull_ThrowsException()
        {
            handler.ProcessStanza(A.Dummy<StandartClientRuntime>(), null);
        }

        [Test]
        public void ProcessStanza_PresenceAvailableNotification_CorrectlyUpdatesPresence()
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, "");

            handler.ProcessStanza(A.Dummy<StandartClientRuntime>(), stanza);

            PresenceStatus status = statusStorage[fromJid];
            Assert.That(status.EntityJid, Is.EqualTo(fromJid));
            Assert.That(status.IsAvailable, Is.True);
        }

        [Test]
        public void ProcessStanza_PresenceUnavailableNotification_CorrectlyUpdatesPresence()
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, PresenceStanzaType.Unavailable);

            handler.ProcessStanza(A.Dummy<StandartClientRuntime>(), stanza);

            PresenceStatus status = statusStorage[fromJid];
            Assert.That(status.IsAvailable, Is.False);
        }

        [Test]
        public void ProcessStanza_AvailabilityStatusNone_CorreclyUpdatesAvailabilityStatus()
        {
            VerifyStatusAvailabilityType("", AvailabilityStatus.None);
        }

        [Test]
        public void ProcessStanza_AvailabilityStatusAway_CorrectlyUpdatesAvailabilityStatus()
        {
            VerifyStatusAvailabilityType(AvailabilityType.Away, AvailabilityStatus.Away);
        }

        [Test]
        public void ProcessStanza_AvailabilityStatusChat_CorrectlyUpdatesAvailabilityStatus()
        {
            VerifyStatusAvailabilityType(AvailabilityType.Chat, AvailabilityStatus.Chat);
        }

        [Test]
        public void ProcessStanza_AvailabilityStatusDoNotDisturb_CorrectlyUpdatesAvailabilityStatus()
        {
            VerifyStatusAvailabilityType(AvailabilityType.DoNotDisturb, AvailabilityStatus.DoNotDisturb);
        }

        [Test]
        public void ProcessStanza_AvailabilityStatusExtendedAway_CorrectlyUpdatesAvailabilityStatus()
        {
            VerifyStatusAvailabilityType(AvailabilityType.ExtendedAway, AvailabilityStatus.ExtendedAway);
        }

        private void VerifyStatusAvailabilityType(string statusType, AvailabilityStatus availabilityType)
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, "", statusType);

            handler.ProcessStanza(A.Dummy<StandartClientRuntime>(), stanza);

            PresenceStatus status = statusStorage[fromJid];
            Assert.That(status.AvailabilityStatus, Is.EqualTo(availabilityType));
        }

        [Test]
        public void ProcessStanza_PresenceNotification_CorrectlyUpdatesStatusDescription()
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, "", "", "some-description");

            handler.ProcessStanza(A.Dummy<StandartClientRuntime>(), stanza);

            PresenceStatus status = statusStorage[fromJid];
            Assert.That(status.AvailabilityDescription, Is.EqualTo("some-description"));
        }

        [Test]
        public void ProcessStanza_SubscriptionRequestStanza_RegistersSubscriptionRequest()
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, PresenceStanzaType.Subscribe);
            handler.ProcessStanza(null, stanza);

            var request = subscriptionRegistry.InboundRequest(fromJid);

            Assert.That(request, Is.Not.Null);
            Assert.That(request.EntityJid, Is.EqualTo(fromJid));
        }

        [Test]
        public void ProcessStanza_SubscriptionAproveStanza_RaisesEventAndRemovesOutboundRequest()
        {
            VerifySubscriptionRequestProcessing("OutboundRequestApproved", PresenceStanzaType.Subscribed);
        }

        [Test]
        public void ProcessStanza_SubscriptionDenyStanza_RaisesEventAndRemovesOutboundRequest()
        {
            VerifySubscriptionRequestProcessing("OutboundRequestDenied", PresenceStanzaType.Unsubscribed);
        }

        private void VerifySubscriptionRequestProcessing(string eventName, string stanzaType)
        {
            subscriptionRegistry.RegisterOutboundRequest(fromJid);
            EventHandler<SubscriptionRequestEventArgs> eventHandler = A.Fake<EventHandler<SubscriptionRequestEventArgs>>();
            EventInfo eventInfo = typeof (PresenceSubscriptionRegistry).GetEvent(eventName);
            eventInfo.AddEventHandler(subscriptionRegistry, eventHandler);
            PresenceSubscriptionRequest request = subscriptionRegistry.OutboundRequest(fromJid);

            VerifySubscriptionRequestProcessing(stanzaType, request, eventHandler);
        }

        private void VerifySubscriptionRequestProcessing(string stanzaType, PresenceSubscriptionRequest request,
                                                         EventHandler<SubscriptionRequestEventArgs> eventHandler)
        {
            PresenceStanza stanza = CreatePresenceStanza(fromJid, stanzaType);
            handler.ProcessStanza(null, stanza);
            SubscriptionRequestEventArgs expectedArgs = new SubscriptionRequestEventArgs(request);
            A.CallTo(() => eventHandler.Invoke(subscriptionRegistry, expectedArgs)).MustHaveHappened();
            CollectionAssert.DoesNotContain(subscriptionRegistry.OutboundRequests, request);
        }

        private PresenceStanza CreatePresenceStanza(JID from, string type, string availability = "", string description = "")
        {
            PresenceStanza stanza = new PresenceStanza(type);
            stanza.From = from;
            stanza.Availability = availability;
            stanza.StatusText = description;
            return stanza;
        }
    }
}
